﻿using System;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using Krile.Kernel.WindowsEx;
using Krile.Imported.Snippets;
using Microsoft.Win32;

namespace Krile
{
    class Program : ApplicationContext
    {
        static Mutex mutex = null;

        #region Entry point
        /// <summary>
        /// Application entry point
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var windir = System.Environment.GetEnvironmentVariable("windir");
            try
            {
                var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5");
                if (reg == null || (int)reg.GetValue("SP", 0) == 0)
                    throw new NullReferenceException();
            }
            catch (Exception)
            {
                MessageBoxEx.Show(Lang.Msg.Startup_VersionError_Context, Lang.Msg.Startup_VersionError, Lang.Msg.Startup_VersionError_Caption, MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                Define.Unsupported = true;
            }
            try
            {
                bool createdNew;
                mutex = new Mutex(true, Application.StartupPath.Replace('\\', '.') + "_Krile_Fairytale", out createdNew);
                if (!createdNew)
                {
                    MessageBoxEx.Show(Lang.Msg.Startup_DuplicateBoot_Context, Lang.Msg.Startup_DuplicateBoot, Lang.Msg.Startup_DuplicateBoot_Caption,  MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                    return;
                }
            }
            catch (AbandonedMutexException ame)
            {
                mutex = ame.Mutex;
            }
            Application.Run(new Program());
            mutex.ReleaseMutex();

        }
        #endregion

        #region Global constructor and destructor

        volatile int initScreenState = 0;
        Forms.Dialogs.Popup.Splash splash = null;

        private void UpdateLoadingLabel(string label)
        {
            if (splash != null && splash.IsHandleCreated)
                splash.SetLoadingLabel(label);
        }

        private void SetTimeoutSplash(int timeout)
        {
            if (splash != null && splash.IsHandleCreated)
                splash.SetTimeout(timeout);
        }

        private void ShowInitScreenAndWait()
        {
            if (System.IO.File.Exists(
                System.IO.Path.Combine(Application.StartupPath, Define.SplashConfigFileName)))
            {
                splash = new Krile.Forms.Dialogs.Popup.Splash();
                splash.Show();
                UpdateLoadingLabel(Lang.Startup.CoreInit);
                splash.Refresh();
                while (initScreenState == 0)
                {
                    Application.DoEvents();
                    Thread.Sleep(0);
                    Application.DoEvents();
                }
            }
        }

        private void EndInit(IAsyncResult iar)
        {
            ((Action)iar.AsyncState).EndInvoke(iar);
            if (splash != null)
            {
                splash.Close();
                if (splash != null)
                    splash.Dispose();
            }
            initScreenState = 2;
        }

        private Program()
        {
            //Global constructor

            //Set application global event handler
            Application.ApplicationExit += new EventHandler(Exit);
#if !DEBUG
            Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
#endif

            #region Splashscreen init
            Action act = new Action(ShowInitScreenAndWait);
            act.BeginInvoke(EndInit, act);
            Application.DoEvents();
            #endregion

            Core.Init();

            #region Security init
            if (Core.Config.Security.StrictSecurity)
            {
                if (!Subsystem.Security.SecureStart())
                {
                    Application.Exit();
                    return;
                }
            }
            else
            {
                Subsystem.Security.DefaultStart();
            }
            #endregion

            #region Checking update
            if (Core.Config.Behavior.AutomaticUpdate)
            {
                UpdateLoadingLabel(Lang.Startup.CheckUpdate);
                double dlver = Subsystem.UpdateChecker.Check();
                if (dlver > Define.Version)
                {
                    //更新あり
                    var curVStr = Define.Version.ToRoundDown(2).ToString("0.00");
                    if (Define.Version * 1000 != ((int)Define.Version * 100) * 10)
                    {
                        curVStr += "." + (int)(Define.Version * 1000) % 10;
                    }
                    var newVStr = dlver.ToRoundDown(2).ToString("0.00");
                    if (dlver * 1000 != ((int)dlver * 100) * 10)
                    {
                        newVStr += "." + (int)(dlver * 1000) % 10;
                    }
                    using (var up = new Forms.Dialogs.Popup.Update(curVStr, newVStr))
                    {
                        if (up.ShowDialog() == DialogResult.OK)
                        {
                            using (var aup = new Forms.Dialogs.Popup.AutoUpDL())
                            {
                                if (aup.ShowDialog() == DialogResult.OK)
                                {
                                    Application.Exit();
                                    return;
                                }
                            }
                        }
                    }
                }
            }

            #endregion

            Subsystem.SetGlobal.SetRenderer();

            UpdateLoadingLabel(Lang.Startup.PrepUI);
            Core.FormManager.CreateForms();
            Core.FormManager.GenerateFormHandle();

            UpdateLoadingLabel(Lang.Startup.LoadingPIn);
            LoadMigemo();
            Core.SetLibBridge();
            Core.PluginManager.LoadPlugins();
            //State restore

            #region Finalize auto update
            if (File.Exists(Path.Combine(Define.AppPath, Define.UpdateExeName)))
            {
                UpdateLoadingLabel(Lang.Startup.FinalizeUpd);
                int wait = 0;
                while (true)
                {
                    wait++;
                    var ps = System.Diagnostics.Process.GetProcesses();
                    bool flag = false;
                    foreach (var p in ps)
                    {
                        if (p.ProcessName == Define.UpdateExeName)
                            flag = true;
                    }
                    if (!flag)
                    {
                        try
                        {
                            File.Delete(Path.Combine(Define.AppPath, Define.UpdateExeName));
                        }
                        catch (UnauthorizedAccessException)
                        {
                            Subsystem.Debugger.AddReport(new Krile.Kernel.Bridges.DebugReport("For security reason, krile can't delete update helper.", Krile.Kernel.Bridges.DebugReport.Priorities.Info));
                        }
                        break;
                    }
                    Thread.Sleep(0);
                    if (wait > 1000)
                        break;
                }
            }
            #endregion

            #region Load previous state
            UpdateLoadingLabel(Lang.Startup.LoadPrev);
            Core.Config.LoadDynamic();
            Core.Config.LoadState();
            var checks = Subsystem.CompatibilityControl.CheckCompatible();
            if (checks != null && checks.Length > 0)
            {
                using (var cd = new Forms.Dialogs.Popup.ConfigCompatible(checks))
                {
                    if (splash != null)
                    {
                        splash.SafeOperate(() =>
                        {
                            splash.AddOwnedForm(cd);
                            cd.ShowDialog();
                        });
                    }
                    else
                    {
                        cd.ShowDialog();
                    }
                    Core.Config.SaveStatic(false);
                }
            }
            Core.KeyAssign.DefreezeAssigns();
            Core.FormManager.TrayForm.Init();
            #endregion

            #region Prepare for system start

            UpdateLoadingLabel(Lang.Startup.StartPIn);
            SetTimeoutSplash(5000);
            Core.PluginManager.StartPlugin();
            #endregion

            #region Firstboot config
            if (Core.Config.IsFirstboot)
            {
                UpdateLoadingLabel(Lang.Startup.WaitFirstBoot);
                using (var key = new Forms.Dialogs.Popup.InitialAssign())
                {
                    if (splash != null)
                    {
                        splash.SafeOperate(() =>
                        {
                            splash.AddOwnedForm(key);
                            key.ShowDialog();
                        });
                    }
                    else
                    {
                        key.ShowDialog();
                    }
                }
                //for Splash screen
                File.Create(Path.Combine(Application.StartupPath, Define.SplashConfigFileName));
            }
            #endregion

            UpdateLoadingLabel(Lang.Startup.InitMainWnd);
            if (Core.Config.Behavior.ShowMainWindowOnStartup)
                Core.FormManager.MainForm.Show();

            #region Splashscreen dispose
            if (initScreenState == 0)
            {
                initScreenState = 1;
                while (initScreenState == 1)
                {
                    Application.DoEvents();
                    Thread.Sleep(0);
                    Application.DoEvents();
                }
            }
            #endregion
        }

#if !PURE_CLR
        void LoadMigemo()
        {
            if (File.Exists(Path.Combine(Define.AppPath, "migemo.dll")))
            {
                if (!Core.Config.Advanced.UseMigemo)
                {
                    MessageBoxEx.Show(Lang.Msg.Migemo_Found_Context, Lang.Msg.Migemo_Found, Lang.Msg.Migemo_Found_Caption,
                                      MessageBoxEx.MessageBoxExButtons.OK, MessageBoxEx.MessageBoxExIcon.Information);
                    using (var ofd = new OpenFileDialog())
                    {
                        ofd.InitialDirectory = Application.StartupPath;
                        ofd.Filter = "Migemo dictionary|migemo-dict";
                        if (ofd.ShowDialog() == DialogResult.OK)
                        {
                            Core.Config.Advanced.MigemoDictLoc = ofd.FileName;
                        }
                        else
                        {
                            Core.Config.Advanced.MigemoDictLoc = null;
                        }
                    }
                }
                Core.Config.Advanced.UseMigemo = true;
                Core.MigemoLib = new KaoriYa.Migemo.Migemo(Core.Config.Advanced.MigemoDictLoc);
            }
            else if (Core.Config.Advanced.UseMigemo)
            {
                MessageBoxEx.Show(Lang.Msg.Migemo_Notfound_Context, Lang.Msg.Migemo_Notfound, Lang.Msg.Migemo_Notfound_Caption,
                                  MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                Core.Config.Advanced.UseMigemo = false;
            }
        }
#else
        void LoadMigemo() { }
#endif
        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            try
            {
                if (Define.Unsupported) return;
                if (e.ExceptionObject is UnauthorizedAccessException)
                {
                    //Unauthorized exception
                    MessageBoxEx.Show(
                        Lang.Msg.ErrorMsg_Security_Context,
                        Lang.Msg.ErrorMsg_Security + e.ExceptionObject.ToString(),
                        Lang.Msg.ErrorMsg_Security_Caption
                        , MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                }
                string destination = System.IO.Path.Combine(Application.StartupPath, System.IO.Path.GetRandomFileName() + ".dbg");
                try
                {
                    Exception excp = (Exception)e.ExceptionObject;
                    using (System.IO.StreamWriter sw = new System.IO.StreamWriter(destination))
                    {
                        sw.WriteLine("**KRILE FATAL ERROR REPORT**");
                        sw.WriteLine("[Krile v." + Define.VersionString + "-" + Define.Build + "]");
                        sw.WriteLine(DateTime.Now.ToString() + " @ " + Environment.OSVersion.VersionString);
                        sw.WriteLine(Environment.NewLine + ">>BODY<<");
                        sw.WriteLine(excp.ToString());
                        sw.WriteLine(Environment.NewLine + ">>BASE<<");
                        sw.WriteLine(excp.GetBaseException());
                    }
                }
                finally
                {
                    MessageBoxEx.Show(
                        Lang.Msg.ErrorMsg_Fatal_Context,
                        Lang.Msg.ErrorMsg_Fatal + destination,
                        Lang.Msg.ErrorMsg_Fatal_Caption,
                        MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                    if (!String.IsNullOrEmpty(Define.DebugAgentFileName) && File.Exists(Path.Combine(Application.StartupPath, Define.DebugAgentFileName)))
                    {
                        System.Diagnostics.Process.Start(Path.Combine(Application.StartupPath, Define.DebugAgentFileName), destination);
                    }
                }
            }
            finally
            {
                Application.Exit();
            }
        }

        void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            if (Define.Unsupported) return;
            Subsystem.Debugger.AddReport(new Krile.Kernel.Bridges.DebugReport(e.Exception, Krile.Kernel.Bridges.DebugReport.Priorities.Fatal));
            if (e.Exception is KrileStrictCheckException)
            {
                MessageBoxEx.Show(
                    "Internal strictly check failed.",
                    e.Exception.ToString(),
                    "Failing strict check", MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
            }
            else if (e.Exception is ObjectDisposedException)
            {
                //Disposing error
            }
            else
            {
                #region ExceptionDialog
                using (var ex = new Forms.Dialogs.Show.ExceptionTracking(e.Exception))
                {
                    var result = ex.ShowDialog();
                    if (result == DialogResult.Abort)
                    {
                        try
                        {
                            Environment.Exit(-1);
                        }
                        catch
                        {
                            Environment.FailFast("Unrecoveriable error occurred.");
                        }
                    }
                    else if (result == DialogResult.Retry)
                    {
                        Application.Restart();
                    }
                }
                #endregion
            }
        }

        void Exit(object sender, EventArgs e)
        {
            //Global destructor
            if (Core.Config == null || Core.Config.Security.SaveErrorReport)
                Subsystem.Debugger.WriteReport(false);
            this.Dispose(true);
        }

        #endregion
    }
}